/*
  Copyright (C) 2011, Kyungjoo Kim
  
  This file is part of LINAL (LINear ALgebra)
  
  All rights reserved.

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.
  
  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.
  
  You should have received a copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
  Also add information on how to contact you by electronic and paper mail.
  
  
  Kyungjoo Kim
  iamkyungjoo@gmail.com
*/
#ifndef LINAL_UTIL_ERROR_HXX
#define LINAL_UTIL_ERROR_HXX


/*!
  Error message
*/

// ** not yet constructed
#define LINAL_ERROR_NOT_YET_IMPLEMENTED \
  ">> Not yet implemented"

// ** obj error
#define LINAL_ERROR_OBJ_CREATED \
  ">> Object is already created"
#define LINAL_ERROR_BASE_OBJ_IS_NULL \
  ">> Base object is NULL"

// ** check datatype
#define LINAL_ERROR_CHECK_INT \
  ">> Datatype is NOT int"
#define LINAL_ERROR_CHECK_SCALAR \
  ">> Datatype is NOT scalar (INT, REAL or COMPLEX)"
#define LINAL_ERROR_CHECK_ELEMTYPE \
  ">> Elemtype is invalid (SCALAR or MATRIX)"
#define LINAL_ERROR_CHECK_DOUBLE_SCALAR \
  ">> Datatype is NOT double precision scalar (INT, REAL or COMPLEX)"
#define LINAL_ERROR_CHECK_SINGLE_SCALAR \
  ">> Datatype is NOT single precision scalar (INT, REAL or COMPLEX)"

#define LINAL_ERROR_CHECK_SCALAR_ELEMTYPE \
  ">> FLA_Obj has null base object or is NOT SCALAR type"
#define LINAL_ERROR_CHECK_MATRIX_ELEMTYPE \
  ">> FLA_Obj has null base object or is NOT MATRIX type"

// ** check control parameter
#define LINAL_ERROR_CHECK_INPUT \
  ">> Invalid input arguments"
#define LINAL_ERROR_CHECK_UPLO \
  ">> Not a valid uplo"
#define LINAL_ERROR_CHECK_DIAG \
  ">> Not a valid diag"
#define LINAL_ERROR_CHECK_SIDE \
  ">> Not a valid side"
#define LINAL_ERROR_CHECK_TRANS \
  ">> Not a valid trans"
#define LINAL_ERROR_CHECK_COMPLEX_INDEX \
  ">> Not a valid index ( real = 0, imag = 1 )"

// ** check rangoe of input
#define LINAL_ERROR_OUT_OF_RANGE \
  ">> Out of range"
#define LINAL_ERROR_OUT_OF_RANGE_FORMAT \
  "%s\n %s %6d %s %6d %s\n %s %6d %s %6d %s\n %s %6d %s %6d %s\n"

// ** check matrix type
#define LINAL_ERROR_A_SQUARE_MATRIX \
  ">> Matrix A should be a square matrix"
#define LINAL_ERROR_B_SQUARE_MATRIX \
  ">> Matrix B should be a square matrix"
#define LINAL_ERROR_C_SQUARE_MATRIX \
  ">> Matrix C should be a square matrix"
#define LINAL_ERROR_MATRIX_DIMENSION_MISMATCH \
  ">> Matrix dimension does not match each other"

// ** check gpu device range
#define LINAL_ERROR_GPU_DEVICE \
  ">> Device ID is invalid"
#define LINAL_ERROR_CUBLAS_FAIL_TO_ALLOC \
  ">> cublasAlloc fail"
#define LINAL_ERROR_CUBLAS_FAIL_TO_GET_MATRIX \
  ">> cublasGetMatrix fail"
#define LINAL_ERROR_CUBLAS_FAIL_TO_SET_MATRIX \
  ">> cublasSetMatrix fail"

namespace linal {

  /*! Error handler
   */

  //#define LINAL_ERROR_DRYRUN
  inline void error(const Int_ eval,
             const Char_ *file, const Int_ line,
             const Char_ *prefix, 
             const Char_ *format,
             ...) {
#ifdef LINAL_ERROR_DRYRUN
    if (eval) {
      Char_ buffer[1024];
      va_list args;
      va_start( args, format );
      vsprintf( buffer, format, args );
      fprintf(stderr, "%s %s, %d\n", prefix, file, line);
      fprintf(stderr, "What to check %s\n", buffer);
    }
#else
    if (!eval) {
      Char_ buffer[1024];
      va_list args;
      va_start( args, format );
      vsprintf( buffer, format, args );
      fprintf(stderr, "%s %s, %d\n", prefix, file, line);
      throw std::logic_error( buffer );
    }
#endif    
  }

#define LINAL_ERROR(eval, msg)						\
  linal::error((eval),__FILE__,__LINE__,"[LINAL ERROR] ", "%s\n", msg);

#define LINAL_ERROR_DETAIL(eval,format,...)				\
  linal::error((eval), __FILE__,__LINE__,"[LINAL ERROR] ", format, __VA_ARGS__);


  inline Bool_  check_all_elemtype(Int_ elem) {
    return ( elem == LINAL_MATRIX ||
             elem == LINAL_SCALAR );
  }

  inline Bool_  check_all_scalar_type(Int_ type) {
    return ( type == LINAL_INT ||
             type == LINAL_SINGLE_REAL ||
             type == LINAL_DOUBLE_REAL ||
             type == LINAL_SINGLE_COMPLEX ||
             type == LINAL_DOUBLE_COMPLEX );
  }

  inline Bool_  check_double_scalr_type(Int_ type) {
    return ( type == LINAL_INT ||
             type == LINAL_DOUBLE_REAL ||
             type == LINAL_DOUBLE_COMPLEX );
  }

  inline Bool_  check_single_scalar_type(Int_ type) {
    return ( type == LINAL_INT ||
             type == LINAL_SINGLE_REAL ||
             type == LINAL_SINGLE_COMPLEX );
  }
  
  inline Bool_  check_side(Int_ side) {
    return ( side == LINAL_LEFT || 
             side == LINAL_RIGHT );
  }

  inline Bool_  check_diag(Int_ diag) {
    return ( diag == LINAL_ZERO_DIAG || 
             diag == LINAL_NONUNIT_DIAG || 
             diag == LINAL_UNIT_DIAG );
  }

  inline Bool_  check_trans_only(Int_ trans) {
    return ( trans == LINAL_TRANSPOSE ||
             trans == LINAL_NO_TRANSPOSE );
  }

  inline Bool_  check_conj_trans_only(Int_ trans) {
    return ( trans == LINAL_CONJ_TRANSPOSE ||
             trans == LINAL_CONJ_NO_TRANSPOSE );
  }
  
  inline Bool_  check_trans_all(Int_ trans) {
    return ( trans == LINAL_TRANSPOSE ||
             trans == LINAL_NO_TRANSPOSE ||
             trans == LINAL_CONJ_TRANSPOSE ||
             trans == LINAL_CONJ_NO_TRANSPOSE );
  }

  inline Bool_  check_uplo(Int_ uplo) {
    return ( uplo == LINAL_LOWER_TRIANGULAR ||
             uplo == LINAL_UPPER_TRIANGULAR );
  }

  inline Bool_  check_complex_index(Int_ index) {
    return ( index == LINAL_COMPLEX_REAL_PART ||
             index == LINAL_COMPLEX_IMAG_PART );
  }

  inline Int_ get_datatype_real(Int_ datatype) {
    Int_ r_val;
    switch (datatype) {
    case FLA_FLOAT:
    case FLA_COMPLEX:        r_val = FLA_FLOAT;  break;
    case FLA_DOUBLE:
    case FLA_DOUBLE_COMPLEX: r_val = FLA_DOUBLE; break;
    case FLA_INT:            r_val = FLA_INT;    break;
    }
    return r_val;
  }
  
}

#endif
